//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Extensibility;
using System.Collections.Generic;

namespace OpenLS.Spreadsheet.AnalysisToolPak
{
    internal delegate T2 Func<T1, T2>(T1 t1);
    class EnumerableHelper
    {
        public static double Product(IEnumerable<double> dbls)
        {
            double result = 1;
            foreach (var d in dbls)
            {
                result *= d;
            }
            return result;
        }
        public static double Sum(IEnumerable<double> dbls)
        {
            double result = 0;
            foreach (var d in dbls)
            {
                result += d;
            }
            return result;
        }
        public static IEnumerable<double> GetFactorialNumbers(double d)
        {
            for (int i = 1  ; i <= d; i++)
            {
                yield return i;
            }
        }
        public static IEnumerable<double> Union (IEnumerable<IEnumerable<double>> ee)
        {
            foreach (var enumerable in ee)
            {
                foreach (var d in enumerable)
                {
                    yield return d;
                }
            }
        }

        public static IEnumerable<T> Select<T>(IEnumerable<double> source, Func<double, T> func)
        {
            foreach (var d in source)
            {
                yield return func(d);
            }
        }
    }
    [AddInFunctions]
    internal class MathFunctions
    {
        public static double FactDouble(double number)
        {
            int i = (int) number;
            if (i < 0)
                throw new NotSupportedException();
            switch (i)
            {
                case 0:
                    return 1;
                case 1:
                    return 1;
                case 2:
                    return 2;
                default:
                    return FactDouble(number - 2)*number;
            }
        }

        public static int GCD(params int[] numbers)
        {
            if (numbers.Length == 0)
                throw new NotSupportedException();
            List<int> nums = new List<int>();
            foreach (var n in numbers)
            {
                if (n != 0)
                    nums.Add(n);
            }
            if (nums.Count == 0)
                return 0;
            if (nums.Count == 1)
                return nums[0];
            nums.Sort();
            int d = GCDImp(nums[0], nums[1]);
            if (nums.Count == 2)
                return d;
            bool ok = true;
            foreach (double value in nums)
                if (value%d != 0)
                {
                    ok = false;
                    break;
                }
            if (ok)
                return d;
            int[] num2 = new int[nums.Count - 1];
            for (int i = 1; i < nums.Count; i++)
                num2[i - 1] = nums[i];
            num2[0] = d;
            return GCD(num2);
        }

        private static int GCDImp(int a, int b)
        {
            Contract.Requires(a != 0);
            Contract.Requires(b != 0);
            int divider, dividend;
            if (a < b)
            {
                divider = a;
                dividend = b;
            }
            else
            {
                divider = b;
                dividend = a;
            }
            if (dividend%divider == 0)
                return divider;
            return GCDImp(divider, dividend%divider);
        }

        public static int Lcm([IgnoreBooleans, IgnoreStrings, IgnoreNulls] params double[] numbers)
        {
            int result = 1;
            foreach (double number in numbers)
            {
                int i = (int) Math.Floor(number);
                if (i <= 0)
                    return 1;
                result /= GCDImp(result, i);
                result *= i;
            }
            return result;
        }

        public static double MRound(double number, double multiple)
        {
            if (multiple * number < 0)
                throw new ErrorException(Errors.NumError);
            if (multiple == 0)
                return 0;
            double d = Math.Round(number/multiple);
            if ((d+0.5 == number/multiple))
                d++;
            return multiple * d;
        }

        public static double MultiNomial([IgnoreNulls, IgnoreStrings, IgnoreBooleans] params double[] numbers)
        {
            double sum = EnumerableHelper.Sum(numbers);

            Stack<double> numerator = new Stack<double>( EnumerableHelper.GetFactorialNumbers(sum));
            Stack<double> denominator = new Stack<double>(
                EnumerableHelper.Union(EnumerableHelper.Select(numbers, n => EnumerableHelper.GetFactorialNumbers(n))));
            double result = 1;
            while (numerator.Count > 0 || denominator.Count > 0)
            {
                if (denominator.Count == 0)
                {
                    result *= numerator.Pop();
                }
                else if (numerator.Count == 0)
                {
                    result /= denominator.Pop();
                }
                else if (Math.Abs(result) < 1)
                { result *= numerator.Pop(); }
                else
                {
                    result /= denominator.Pop();
                }
            }
            return result;

        }

        public static object Quotient(int numerator, int denominator)
        {
            if (denominator == 0)
                return Errors.DivError;
            return numerator/denominator;
        }

        public static object RandBetween(double bottom, double top)
        {
            if (bottom > top)
                return Errors.NumError;
            return (int)(bottom + (top - bottom) * Rand());
        }

        private static double Rand()
        {
            return new Random(Guid.NewGuid().GetHashCode()).NextDouble();
        }

        public static double SqrtPi(double number)
        {
            return Math.Sqrt(number*Math.PI);
        }

        public static double SeriesSum(double x, double n, double m, params  double[] coefficients)
        {
            double factor = Math.Pow(x, m);
            double result = 0;
            x = Math.Pow(x, n);
            foreach (var c in coefficients)
            {
                result += c * x;
                x *= factor;
            }
            return result;
        }
    }
}